#ifndef __MEMLEAK_H__
#define __MEMLEAK_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static inline void raw_free(void *x) { free(x); }
static inline char *raw_strdup(const char *s) { return strdup(s); }

#define MEMLEAK_INIT do {} while(0)
#ifdef DBG_MEM
#include "atomic.h"
#include "list.h"
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#ifdef ENABLE_STAT_THREAD
#include "connect_to.h"
#endif

struct mem_node {
    struct list_head node;
    atomic64_t alloc_count;
    atomic64_t free_count;
    atomic64_t mem_count;
    const char *file;
    const char *func;
    int line;
};

struct memleak_handle {
    struct memleak_handle *me_addr;
    struct list_head mem_node_list;
    pthread_mutex_t lock;
};

extern struct memleak_handle memleak_handle;
#undef  MEMLEAK_INIT
#define MEMLEAK_INIT do {\
    if (memleak_handle.me_addr == &memleak_handle) { printf("memleak_handle inited, can not call me again %s %s %u\n", __FILE__, __func__, __LINE__); abort(); }\
    memleak_handle.me_addr = &memleak_handle; \
    INIT_LIST_HEAD(&memleak_handle.mem_node_list);\
    pthread_mutex_init(&memleak_handle.lock, NULL);\
} while(0)

static inline uint64_t memleak_mem_total()
{
    uint64_t mem_total = 0;
    struct mem_node *node;
    list_for_each_entry(node, &memleak_handle.mem_node_list, node) {
        mem_total += atomic64_read(&node->mem_count);
    }
    return mem_total;
}

static inline int memleak_stat(FILE *fp, int is_html_format, char *buf, int buf_len)
{
    int data_len = 0;
    struct mem_node *node;
    char tmp[4096];
    list_for_each_entry(node, &memleak_handle.mem_node_list, node) {
        tmp[0] = 0;
        uint64_t n;
        n = atomic64_read(&node->mem_count);                                    sprintf(tmp + strlen(tmp), "mem %-10"PRIu64, n);
        n = atomic64_read(&node->alloc_count)-atomic64_read(&node->free_count); sprintf(tmp + strlen(tmp), " count %-9"PRIu64, n);
        n = atomic64_read(&node->alloc_count);                                  sprintf(tmp + strlen(tmp), "%9"PRIu64   , n);
        n = atomic64_read(&node->free_count);                                   sprintf(tmp + strlen(tmp), "-%-9"PRIu64, n);
        if (fp) {
            fprintf(fp, "FUNC %-40.40s line %-4d %s file %s \n", node->func, node->line, tmp, node->file);
        } else if (buf) {
            data_len += snprintf(buf + data_len, buf_len - data_len, "FUNC %-40.40s line %-4d %s file %s%s\n", node->func, node->line, tmp, node->file, is_html_format ? "<br>" : "");
            if (data_len >= buf_len) data_len = buf_len-1;
            buf[data_len] = 0;
        } else {
            printf("FUNC %-40.40s line %-4d %s file %s\n", node->func, node->line, tmp, node->file);
        }
    }
    return data_len;
}

#define MEMLEAK_HEAD_SIZE (8+sizeof(long))
#define check_overleap(x) do { \
        uint8_t *u8 = (uint8_t *)(x); int *_m = (int *)(u8 - MEMLEAK_HEAD_SIZE); if (_m[0] != 0x01020304) { printf("checkout MEMLEAK_HEAD crash file %s func line %s %u\n", __FILE__, __func__, __LINE__); abort(); }\
        uint8_t *end=u8 + _m[1]; if (memcmp(end, "\x04\x03\x02\x01", 4)) { printf("checkout MEMLEAK_TAIL crash file %s func %s line %u\n", __FILE__, __func__, __LINE__); abort(); } \
} while(0)

//#define DEBUG_LOG(fmt, ...) printf("%-20s %-20s %-4d "fmt, __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#define DEBUG_LOG(...)
#define free(x) ({ \
        DEBUG_LOG("freeing %p\n", x); \
        check_overleap(x); \
        uint8_t *u8 = (uint8_t *)(x); int *_m = (int *)(u8 - MEMLEAK_HEAD_SIZE); \
        struct mem_node *node; memcpy(&node, _m+2, sizeof(long)); atomic64_inc(&node->free_count); atomic64_sub(&node->mem_count, _m[1]); \
        raw_free(_m); })
#define malloc(x) ({ \
        static struct mem_node node = { {0,0}, {0}, {0}, {0}, __FILE__, __func__, __LINE__ }; \
        if (node.node.next == NULL) { pthread_mutex_lock(&memleak_handle.lock); if (node.node.next == NULL) list_add_tail(&node.node, &memleak_handle.mem_node_list); pthread_mutex_unlock(&memleak_handle.lock); } \
        atomic64_inc(&node.alloc_count); \
        int *_m = malloc(MEMLEAK_HEAD_SIZE + (x) + 4); uint8_t *end = (uint8_t *)_m; end += MEMLEAK_HEAD_SIZE + (x); \
        if (_m) { atomic64_add(&node.mem_count, x); _m[0] = 0x01020304; _m[1] = x; memcpy(end, "\x04\x03\x02\x01", 4); void **p = (void **)(_m+2); *p = &node; _m += 2 + sizeof(long) / 4; } \
        DEBUG_LOG("malloc out %p\n", _m); \
        (void *)_m; })
#define calloc(_x,_y) ({ int x = _x; int y = _y; \
        static struct mem_node node = { {0,0}, {0}, {0}, {0}, __FILE__, __func__, __LINE__ }; \
        if (node.node.next == NULL) { pthread_mutex_lock(&memleak_handle.lock); if (node.node.next == NULL) list_add_tail(&node.node, &memleak_handle.mem_node_list); pthread_mutex_unlock(&memleak_handle.lock); } \
        atomic64_inc(&node.alloc_count); \
        int *_m = calloc(1, MEMLEAK_HEAD_SIZE + (x)*(y) + 4); uint8_t *end = (uint8_t *)_m; end += MEMLEAK_HEAD_SIZE + (x)*(y); \
        if (_m) { atomic64_add(&node.mem_count, (x)*(y)); _m[0] = 0x01020304; _m[1] = (x)*(y); memcpy(end, "\x04\x03\x02\x01", 4); void **p = (void **)(_m+2); *p = &node; _m += 2 + sizeof(long) / 4; } \
        DEBUG_LOG("calloc out %p\n", _m); \
        (void *)_m; })
#define realloc(x,y) ({ \
        static struct mem_node node = { {0,0}, {0}, {0}, {0}, __FILE__, __func__, __LINE__ }; \
        if (node.node.next == NULL) { pthread_mutex_lock(&memleak_handle.lock); if (node.node.next == NULL) list_add_tail(&node.node, &memleak_handle.mem_node_list); pthread_mutex_unlock(&memleak_handle.lock); } \
        atomic64_inc(&node.alloc_count); \
        int *_m = NULL; if (x) { uint8_t *u8 = (uint8_t *)(x); _m = (int *)(u8 - MEMLEAK_HEAD_SIZE); check_overleap(x); } \
        int *n = realloc(_m, MEMLEAK_HEAD_SIZE + (y) + 4); uint8_t *end = (uint8_t *)n; end += MEMLEAK_HEAD_SIZE + (y); \
        if (n) { \
            if (_m) { struct mem_node *node; memcpy(&node, n+2, sizeof(long)); atomic64_inc(&node->free_count); atomic64_sub(&node->mem_count, n[1]); } \
            atomic64_add(&node.mem_count, y); n[0] = 0x01020304; n[1] = y; memcpy(end, "\x04\x03\x02\x01", 4); void **p = (void **)(n+2); *p = &node; n += 2 + sizeof(long) / 4; \
        } \
        DEBUG_LOG("realloc %p out %p\n", x, n); \
        (void *)n; })
#undef  strdup
#define strdup(s) ({ char *_m = malloc(strlen(s)+1); if (_m) strcpy(_m, s); DEBUG_LOG("strdup out %p\n", _m); _m; })
#undef  strndup
#define strndup(s,n) ({ int s_l = strlen(s); char *_m; if (s_l > n) { _m = malloc(n+1); if (_m) { memcpy(_m, s, n); _m[n] = 0; } } else _m = strdup(s); DEBUG_LOG("strndup out %p\n", _m); _m; })

#endif
#ifdef ENABLE_STAT_THREAD
static inline void *memleak_stat_worker(void *arg)
{
    FILE *fp = (FILE *)arg;
    LOGERR("start\n");
    pthread_detach(pthread_self());
    int read_len = 0;
    char buf[4096];
    for (;;) {
        int n = fread(buf + read_len, 1, 4 - read_len, fp); if (n <= 0) break;
        read_len += n;
        if (read_len >= 4) {
            read_len = 0;
            memleak_stat(fp, 0, NULL, 0);
            fprintf(fp, "mem_total %"PRIu64"\n", memleak_mem_total());
        }
    }
    fclose(fp);
    LOGERR("end\n");
    return NULL;
}

static inline void *memleak_stat_thread(void *arg)
{
    char **p = arg;
    char *host = p[0];
    uint16_t port = atoi(p[1]);
    int listen_fd = port_to_listen_fd(host, port); if (listen_fd < 0) { LOGERR("set up listen port 10 error[%m]\n"); exit(2); }
    for (;;) {
        struct sockaddr addr; socklen_t addrlen;
        int fd = accept(listen_fd, &addr, &addrlen);
        if (fd <= -1) { LOGERR("accept error[%m], goto listen again\n"); sleep(1); continue; }
        FILE *fp = fdopen(fd, "r+"); if (fp == NULL) { LOGERR("fail to fdopen\n"); close(fd); continue; }
        pthread_t pt;
        if (pthread_create(&pt, NULL, memleak_stat_worker, fp)) { LOGERR("pthread_create error\n"); fclose(fp); continue; }
        sleep(1);
    }
}
#endif

#define TRY_ALLOC do { char *p = malloc(1000); free(p); } while(0)
#endif
